{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b07a3b47-2262-4135-a1d2-52e8392b44eb",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "if \"pyodide\" in sys.modules:\n",
    "    import piplite\n",
    "    await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "49c3f9ea-23ce-4c5c-b3fe-44f1cecadf20",
   "metadata": {},
   "source": [
    "pyb2d is imported as b2d"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dff93359-2c68-467a-9239-478a0e550a4b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import b2d\n",
    "# import pyb2d_jupyterlite_backend\n",
    "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n",
    "import numpy as np\n",
    "import matplotlib.pylab as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc977c4e-75ee-4349-9408-650c3dcd01e0",
   "metadata": {},
   "source": [
    "# Tutorial 0: A free falling body\n",
    "The first step with Box2D is the creation of the world. The world is parametrized by a gravity vector."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4ff914a6-eb18-45a1-b1ed-e8ad7ab0d298",
   "metadata": {},
   "outputs": [],
   "source": [
    "# the world\n",
    "gravity = (0, -10)\n",
    "world = b2d.World(gravity)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3afdbb2a-e694-4779-b95e-73a5b38d34b6",
   "metadata": {},
   "source": [
    "Create a circle-shaped body"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "99837a63-4628-483c-8f2d-cc4aec9cb1d5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# the body def\n",
    "body_def = b2d.BodyDef()\n",
    "body_def.type = b2d.BodyType.dynamic\n",
    "body_def.position = (0, 0)\n",
    "\n",
    "# the body\n",
    "body = world.create_body(body_def)\n",
    "\n",
    "# shape\n",
    "circle_shape = b2d.CircleShape()\n",
    "circle_shape.radius = 1.0\n",
    "\n",
    "# the fixture\n",
    "fixture_def = b2d.FixtureDef()\n",
    "fixture_def.shape = circle_shape\n",
    "fixture_def.density = 1.0\n",
    "\n",
    "# create and add the fixture to the body\n",
    "fixture = body.create_fixture(fixture_def)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf9758a6-fb6e-4f9c-b15f-783f9488cf7e",
   "metadata": {},
   "source": [
    "We can now have a look at the world: We render the world st. each meter in the Box2D world will be 100 pixels in the image:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8b433892-3c82-43be-a085-eda3e4279b2c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# from b2d.plot import render_world\n",
    "b2d.plot.plot_world(world, ppm=100)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e1db1f1-6e47-454c-9ea9-86262d7da309",
   "metadata": {},
   "source": [
    "Lets run the world for a total of 5 seconds. \n",
    "Usually one wants to run the world at a certain frame rate.\n",
    "With the frame rate and the total time we can compute the delta for each iteration and how many steps we need"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "41a232a9-a3c5-425d-9aed-d3adb90d6314",
   "metadata": {},
   "outputs": [],
   "source": [
    "t = 5\n",
    "fps = 40\n",
    "dt = 1.0 / fps\n",
    "n_steps = int(t / dt + 0.5)\n",
    "print(f\"t={t} fps={fps} dt={dt} n_steps={n_steps}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4d458acb-6d5c-47ba-bcbf-d15ea2cf2537",
   "metadata": {},
   "source": [
    "in each step we query the bodies position and velocity and store then for later plotting"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4e042c7b-07a7-445f-ba04-e38173b46c0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "positions = np.zeros([n_steps, 2])\n",
    "velocites = np.zeros([n_steps, 2])\n",
    "timepoints = np.zeros([n_steps])\n",
    "\n",
    "t_elapsed = 0.0\n",
    "for i in range(n_steps):\n",
    "\n",
    "    # get the bodies center of mass\n",
    "    positions[i, :] = body.world_center\n",
    "\n",
    "    # get the bodies velocity\n",
    "    velocites[i, :] = body.linear_velocity\n",
    "\n",
    "    timepoints[i] = t_elapsed\n",
    "\n",
    "    world.step(time_step=dt, velocity_iterations=1, position_iterations=1)\n",
    "    t_elapsed += dt"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ec7d66c-c979-40fa-8af3-9e99873ec105",
   "metadata": {},
   "source": [
    "plot the y-position against the time. We can see that the body is falling down in an accelerating way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "434cb907-1b76-414e-bb5e-6ea32dd1f829",
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot(timepoints, positions[:, 1])\n",
    "plt.ylabel('y-poistion [meter]')\n",
    "plt.xlabel('t [sec]')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7f58954-4ea1-49f9-b0b0-7df38336860d",
   "metadata": {},
   "source": [
    "as expected the x position is not changing since the gravity vector is non-zero only in the x direction"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39573eed-e6c8-45bf-8e35-4251b660ce3f",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "plt.plot(timepoints, positions[:, 0])\n",
    "plt.ylabel('x-poistion [meter]')\n",
    "plt.xlabel('t [sec]')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cdb98dc5-3bc8-4933-91a0-a1db3afb9c34",
   "metadata": {},
   "source": [
    "# Tutorial 1: A  falling body in a box, more pythonic\n",
    "Create a world, but in a more pythonic way, and animate the world"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d58c2639-21da-490b-8dcd-205962f63dfc",
   "metadata": {},
   "outputs": [],
   "source": [
    "# the world\n",
    "world = b2d.world(gravity=(0, -10))\n",
    "\n",
    "# create the dynamic body\n",
    "body = world.create_dynamic_body(\n",
    "    position=(5, 5),\n",
    "    fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=1), density=1, restitution=0.75),\n",
    ")\n",
    "\n",
    "# create a box\n",
    "box_shape = b2d.ChainShape()\n",
    "box_shape.create_loop([(0, 0), (0, 10),(10,10),(10, 0)])\n",
    "box = world.create_static_body(\n",
    "    position=(0, 0), fixtures=b2d.fixture_def(shape=box_shape, friction=0)\n",
    ")\n",
    "b2d.plot.animate_world(world, ppm=20, t=10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10dbb85a-84b0-4820-8fb3-6108d9c0fe00",
   "metadata": {},
   "source": [
    "note that when we animate that world again, the body has already been fallen"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c919702c-7c62-4d87-bf1f-df5027d72a83",
   "metadata": {},
   "outputs": [],
   "source": [
    "b2d.plot.animate_world(world, ppm=20, t=2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7322e9c5-8608-4375-81ed-766cbb2af927",
   "metadata": {},
   "source": [
    "# Tutorial 2: Interactive worlds\n",
    "While animating the world already is already nice, interacting with the world is even better.\n",
    "pyb2d has a framwork to interact with the world for multiple backends.\n",
    "This framework is called `TestbedBase` since you can \"test\" your world in an interactive way"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "97cddd47-5a88-4cae-8543-cfcdf658255a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from b2d.testbed import TestbedBase\n",
    "\n",
    "class InteractiveExample(TestbedBase):\n",
    "    def __init__(self, settings=None):\n",
    "        super(InteractiveExample, self).__init__(settings=settings)\n",
    "        # create two balls\n",
    "        body = self.world.create_dynamic_body(position=(5, 5),\n",
    "            fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=1), density=1, restitution=0.5),\n",
    "        )\n",
    "        body = self.world.create_dynamic_body(position=(8, 5),\n",
    "            fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=1), density=1, restitution=0.8),\n",
    "        )\n",
    "        # create a box\n",
    "        box_shape = b2d.ChainShape()\n",
    "        box_shape.create_loop([(0, 0), (0, 10),(10,10),(10, 0)])\n",
    "        box = self.world.create_static_body(\n",
    "            position=(0, 0), fixtures=b2d.fixture_def(shape=box_shape, friction=0)\n",
    "        )\n",
    "        \n",
    "s = JupyterAsyncGui.Settings()\n",
    "s.resolution = [300,300]\n",
    "b2d.testbed.run(InteractiveExample, backend=JupyterAsyncGui, gui_settings=s);"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64bf55d1-4117-4af0-8f1f-65de33751743",
   "metadata": {
    "tags": []
   },
   "source": [
    "# Tutorial 3: Joints"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07147cab-23be-4406-85f7-4b3d174e3954",
   "metadata": {
    "tags": []
   },
   "source": [
    "## Tutorial 3.1: Prismatic Joint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9a2d178d-33d7-4c51-b0ff-f66c98cac673",
   "metadata": {},
   "outputs": [],
   "source": [
    "world = b2d.world(gravity=(0, -10))\n",
    "anchor_body = world.create_static_body(position=(0, 0))\n",
    "b = world.create_dynamic_body(\n",
    "    position=(10, 10),\n",
    "    fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[2, 0.5]), density=1),\n",
    "    linear_damping=0.0,\n",
    "    angular_damping=0.0,\n",
    ")\n",
    "world.create_prismatic_joint(anchor_body, b, local_axis_a=(1, 1))\n",
    "b2d.plot.animate_world(world, ppm=20, t=3, bounding_box=((0,0),(10,10)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07971d69-b2ef-4d74-8c1c-48f38dcc708c",
   "metadata": {
    "tags": []
   },
   "source": [
    "## Tutorial 3.2: Pully Joint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39b7fef2-4b4b-4904-9899-1a34f1039693",
   "metadata": {},
   "outputs": [],
   "source": [
    "world = b2d.world(gravity=(0, -10))\n",
    "\n",
    "\n",
    "a = world.create_dynamic_body(\n",
    "    position=(-5, 0),\n",
    "    fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[2, 0.8]), density=1),\n",
    "    linear_damping=0.0,\n",
    "    angular_damping=0.0,\n",
    ")\n",
    "b = world.create_dynamic_body(\n",
    "    position=(5, 0),\n",
    "    fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[2, 0.5]), density=1),\n",
    "    linear_damping=0.0,\n",
    "    angular_damping=0.0,\n",
    ")\n",
    "world.create_pully_joint(\n",
    "    a,\n",
    "    b,\n",
    "    length_a=10,\n",
    "    length_b=10,\n",
    "    ground_anchor_a=(-5, 10),\n",
    "    ground_anchor_b=(5, 10),\n",
    "    local_anchor_a=(0, 0),\n",
    "    local_anchor_b=(0, 0),\n",
    ")\n",
    "b2d.plot.animate_world(world, ppm=20, t=5, bounding_box=((-10,-12),(10,12)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e8fb17d-1dda-45cb-98df-6b98be5b4e6c",
   "metadata": {},
   "source": [
    "## Tutorial 3.3: Revolute Joint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a2686e5f-3fa2-412d-8a40-2e9a67123d43",
   "metadata": {},
   "outputs": [],
   "source": [
    "world = b2d.world(gravity=(0, -10))\n",
    "bodies = []\n",
    "b = world.create_static_body(position=(0, 15))\n",
    "bodies.append(b)\n",
    "for i in range(5):\n",
    "    b = world.create_dynamic_body(\n",
    "        position=(i * 4 + 2, 15),\n",
    "        fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[2, 0.5]), density=1),\n",
    "        linear_damping=0.0,\n",
    "        angular_damping=0.0,\n",
    "    )\n",
    "    bodies.append(b)\n",
    "world.create_revolute_joint(\n",
    "    bodies[0], bodies[1], local_anchor_a=(0, 0), local_anchor_b=(-2, 0.0)\n",
    ")\n",
    "for i in range(1, len(bodies) - 1):\n",
    "    a = bodies[i]\n",
    "    b = bodies[i + 1]\n",
    "    world.create_revolute_joint(a, b, local_anchor_a=(2, 0.0), local_anchor_b=(-2, 0.0))\n",
    "b2d.plot.animate_world(world, ppm=20, t=5, bounding_box=((-20,-10),(20,20)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9caa18f0-4eb7-4e72-8445-8f6d096d9465",
   "metadata": {
    "tags": []
   },
   "source": [
    "## Tutorial 3.4: Weld Joint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3d74fb2f-7da5-4ad7-8f21-fba1f97acee2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# the world\n",
    "world = b2d.world(gravity=(0, -10))\n",
    "\n",
    "\n",
    "bodies = []\n",
    "\n",
    "# create  a static body as anchor\n",
    "b = world.create_static_body(\n",
    "    position=(0, 4), fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[0.3, 0.5]))\n",
    ")\n",
    "bodies.append(b)\n",
    "\n",
    "for i in range(4):\n",
    "    b = world.create_dynamic_body(\n",
    "        position=(i + 1.0, 4),\n",
    "        fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[0.3, 0.5]), density=0.1),\n",
    "        linear_damping=2.5,\n",
    "        angular_damping=2.5,\n",
    "    )\n",
    "    bodies.append(b)\n",
    "\n",
    "for i in range(len(bodies) - 1):\n",
    "    a = bodies[i]\n",
    "    b = bodies[i + 1]\n",
    "    world.create_weld_joint(\n",
    "        a,\n",
    "        b,\n",
    "        local_anchor_a=(0.5, 0.5),\n",
    "        local_anchor_b=(-0.5, 0.5),\n",
    "        damping=0.1,\n",
    "        reference_angle=0,\n",
    "        stiffness=20,\n",
    "    )\n",
    "    world.create_weld_joint(\n",
    "        a,\n",
    "        b,\n",
    "        local_anchor_a=(0.5, -0.5),\n",
    "        local_anchor_b=(-0.5, -0.5),\n",
    "        damping=0.1,\n",
    "        reference_angle=0,\n",
    "        stiffness=20,\n",
    "    )\n",
    "b2d.plot.animate_world(world, ppm=20, t=5, bounding_box=((0,-5),(5,5)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7373461e-d1fa-4ad9-aeaa-048287839fd9",
   "metadata": {},
   "source": [
    "## Tutorial 3.5: Wheel Joint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1eb711e0-ae53-43ed-b0c1-c0a2fe42b407",
   "metadata": {},
   "outputs": [],
   "source": [
    "world = b2d.world(gravity=(0, -10))\n",
    "edge = world.create_static_body(\n",
    "    position=(0, 0), fixtures=b2d.fixture_def(shape=b2d.edge_shape([(-20, 0), (5, 0)]))\n",
    ")\n",
    "\n",
    "# random slope\n",
    "x = np.linspace(5, 50, 10)\n",
    "y = np.random.rand(10) * 4 - 2\n",
    "y[0] = 0\n",
    "xy = np.stack([x, y]).T\n",
    "xy = np.flip(xy, axis=0)\n",
    "edge = world.create_static_body(\n",
    "    position=(0, 0),\n",
    "    fixtures=b2d.fixture_def(shape=b2d.chain_shape(xy, prev_vertex=(10, 0))),\n",
    ")\n",
    "# create car\n",
    "left_wheel = world.create_dynamic_body(\n",
    "    position=(-3, 2),\n",
    "    fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=2), density=1),\n",
    ")\n",
    "right_wheel = world.create_dynamic_body(\n",
    "    position=(3, 2),\n",
    "    fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=2), density=1),\n",
    ")\n",
    "\n",
    "chasis = world.create_dynamic_body(\n",
    "    position=(0, 2),\n",
    "    fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[3, 0.5]), density=1),\n",
    ")\n",
    "\n",
    "wheel_joint_def = dict(\n",
    "    stiffness=10,\n",
    "    enable_motor=True,\n",
    "    motor_speed=-100,\n",
    "    max_motor_torque=100,\n",
    "    collide_connected=False,\n",
    "    enable_limit=True,\n",
    "    lower_translation=-0.4,\n",
    "    upper_translation=0.4,\n",
    "    local_axis_a=(0, 1),\n",
    ")\n",
    "world.create_wheel_joint(chasis, left_wheel, local_anchor_a=(-3, 0), **wheel_joint_def)\n",
    "world.create_wheel_joint(chasis, right_wheel, local_anchor_a=(3, 0), **wheel_joint_def)\n",
    "\n",
    "\n",
    "b2d.plot.animate_world(world, ppm=20, t=15, bounding_box=((-10,-5),(20,5)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d9a0fffc-ae40-47ec-b185-2a6fe0dde496",
   "metadata": {},
   "source": [
    "## Tutorial 3.6: Distance Joint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3c121398-f08f-4ea0-a875-de141ba53508",
   "metadata": {},
   "outputs": [],
   "source": [
    "world = b2d.world(gravity=(0, -10))\n",
    "\n",
    "for i in range(10):\n",
    "\n",
    "    # create static anchor (does not need shape/fixture)\n",
    "    anchor = world.create_static_body(position=(i, 0))\n",
    "\n",
    "    # 5 below the anchor\n",
    "    body = world.create_dynamic_body(\n",
    "        position=(i, -10),\n",
    "        fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=0.4), density=0.5),\n",
    "    )\n",
    "\n",
    "    # distance joints of various stiffness-es\n",
    "    world.create_distance_joint(anchor, body, length=10, stiffness=0.5 * (i + 1))\n",
    "\n",
    "b2d.plot.animate_world(world, ppm=20, t=10, bounding_box=((-2,-20),(10,0)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb6afaff-8236-4206-85c7-3ba2de466ba9",
   "metadata": {},
   "source": [
    "# Tutorial 4: Particles"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f5c3b83-51d9-47cb-9b73-d5f8b0e03a76",
   "metadata": {},
   "outputs": [],
   "source": [
    "world = b2d.world(gravity=(0, -10))\n",
    "pdef = b2d.particle_system_def(radius=0.1)\n",
    "psystem = world.create_particle_system(pdef)\n",
    "\n",
    "emitter_pos = (0, 0)\n",
    "emitter_def = b2d.RandomizedLinearEmitterDef()\n",
    "emitter_def.emite_rate = 400\n",
    "emitter_def.lifetime = 5.1\n",
    "emitter_def.size = (2, 1)\n",
    "emitter_def.velocity = (6, 20)\n",
    "emitter = b2d.RandomizedLinearEmitter(psystem, emitter_def)\n",
    "b2d.plot.animate_world(world, ppm=20, t=10, bounding_box=((-10,-20),(20,5)), pre_step=emitter.step)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ea9d7882-d3a4-45bb-b59b-cb1c9cd33990",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
